-module(rtps_basic_SUITE).

%%% ============================================================================
%%% Create and delete basic RTPS entities: domain, participant, writers
%%% and readers. 
%%% ============================================================================

-include_lib("common_test/include/ct.hrl").
-include("../src/rtps.hrl").
-include("../src/rtps_udp.hrl").

-export([all/0, suite/0, groups/0,
	 init_per_suite/1, end_per_suite/1, 
	 init_per_group/2, end_per_group/2,
	 init_per_testcase/2, end_per_testcase/2]).

-compile(export_all).

-define(DOMAIN_ID, 0).
-define(GUID_PREFIX, <<1:96>>).
-define(ENTITY_KEY, <<1:24>>).
-define(REMOTE_GUID_PREFIX, <<9:96>>).
-define(REMOTE_ENTITY_KEY, <<9:24>>).
-define(REMOTE_GUID, #guid{guid_prefix = ?REMOTE_GUID_PREFIX, entity_id = ?REMOTE_ENTITY_KEY}).
%% Remte address and port are used for writer locator
-define(REMOTE_ADDRESS, "239.255.1.1").
-define(REMOTE_PORT, 7777).

suite() ->
    [{timetrap, {minutes, 1}}].

all() -> 
    [domain,
     participant,
     {group, stateless},
     {group, stateful}
    ].

groups() ->
    [{stateless,       [], [{group, best_effort}, {group, reliable}]},
     {stateful,        [], [{group, best_effort}, {group, reliable}]},
     {best_effort,     [], [writer, reader]},
     {reliable,        [], [writer, reader]}
    ].

init_per_suite(Config) ->
    ok = application:start(rtps),
    Config.

end_per_suite(_Config) ->
    application:stop(rtps),
    ok.

%%%===================================================================
%%% Init per group
%%%===================================================================

init_per_group(State_type, Config)
  when State_type =:= stateless;
       State_type =:= stateful ->
    [{state_type, State_type} | Config];
init_per_group(Reliability_level, Config)
  when Reliability_level =:= best_effort;
       Reliability_level =:= reliable ->
    [{reliability_level, Reliability_level} | Config];
init_per_group(_Group, Config) ->
    Config.

%%%===================================================================
%%% End per group
%%%===================================================================

end_per_group(_Group, Config) ->
    Config.

%%%===================================================================
%%% Init per case
%%%===================================================================

init_per_testcase(participant, Config) ->
    {ok, _Pid} = rtps:add_domain(?DOMAIN_ID),
    Config;
init_per_testcase(Case, Config) when Case =:= writer; Case =:= reader->
    State_type = ?config(state_type, Config),
    Reliability_level = ?config(reliability_level, Config),
    case {State_type, Reliability_level} of
	{stateless, reliable} when Case =:= reader ->
	    {skip, "Combination of stateless and reliable does not apply for readers"};
	_ ->
	    Opts = [{state_type, State_type}, {reliability_level, Reliability_level},
		    {multicast_loop, true}],
	    {ok, _} = rtps:add_domain(?DOMAIN_ID),
	    {ok, _} = rtps:add_participant(?DOMAIN_ID, ?GUID_PREFIX),
	    [{opts, Opts} | Config]
    end;
init_per_testcase(_Case, Config) ->
    Config.

%%%===================================================================
%%% End per case
%%%===================================================================

end_per_testcase(_Case, _Config) ->
    ok = rtps:del_domain(?DOMAIN_ID),
    ok.

%%%===================================================================
%%% Test cases
%%%===================================================================

%% @doc Check starting and stopping a domain
domain(_Config) ->
    {ok, _Pid} = rtps:add_domain(?DOMAIN_ID),
    true = is_pid(rtps:whereis(?DOMAIN_ID)),
    rtps:del_domain(?DOMAIN_ID).

%% @doc Check starting and stopping a participant
participant(_Config) ->
    {ok, _Pid} = rtps:add_participant(?DOMAIN_ID, ?GUID_PREFIX),
    true = is_pid(rtps:whereis(?DOMAIN_ID, ?GUID_PREFIX)),
    true = check_udp_port(discovery, ?DOMAIN_ID),
    rtps:del_participant(?DOMAIN_ID, ?GUID_PREFIX).

writer(Config) ->
    State_type = ?config(state_type, Config),
    Opts = ?config(opts, Config),
    {ok, _} = rtps:add_writer(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY, Opts),
    Writer_pid = rtps:whereis_writer(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY),
    true = is_pid(Writer_pid),
    case State_type of
	stateless ->
	    {ok, IPv4_address} = inet:parse_ipv4_address(?REMOTE_ADDRESS),
	    Locator = #locator_udpv4{address = IPv4_address, port = ?REMOTE_PORT},
	    {ok, _RL_pid} = rtps_writer:reader_locator_add(Writer_pid, Locator),
	    true = check_udp_port(?REMOTE_ADDRESS, ?REMOTE_PORT),
	    ok = rtps_writer:reader_locator_remove(Writer_pid, Locator);
	stateful  ->
	    Remote_reader = ?REMOTE_GUID,
	    {ok, _RP_pid} = rtps_writer:matched_reader_add(Writer_pid, Remote_reader),
	    true = check_udp_port(user, ?DOMAIN_ID),
	    ok = rtps_writer:matched_reader_remove(Writer_pid, Remote_reader)
    end,
    ok = rtps:del_writer(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY).
	    
reader(Config) ->
    State_type = ?config(state_type, Config),
    Opts = ?config(opts, Config),
    {ok, _} = rtps:add_reader(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY, Opts),
    Reader_pid = rtps:whereis_reader(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY),
    true = is_pid(Reader_pid),
    case State_type of
	stateless ->
	    true = check_udp_port(user, ?DOMAIN_ID);
	stateful ->
	    Remote_writer = ?REMOTE_GUID,
	    {ok, _WP_pid} = rtps_reader:matched_writer_add(Reader_pid, Remote_writer),
	    true = check_udp_port(user, ?DOMAIN_ID),
	    ok = rtps_reader:matched_writer_remove(Reader_pid, Remote_writer)
    end,
    ok = rtps:del_reader(?DOMAIN_ID, ?GUID_PREFIX, ?ENTITY_KEY).

%%%===================================================================
%%% Internal functions
%%%===================================================================

check_udp_port(discovery, Domain_id) ->
    check_udp_port(?DEFAULT_UDPv4_MULTICAST_ADDRESS, ?PB + ?DG * Domain_id + ?d0);
check_udp_port(user, Domain_id) ->
    check_udp_port(?DEFAULT_UDPv4_MULTICAST_ADDRESS, ?PB + ?DG * Domain_id + ?d2);
check_udp_port(Address, Port) ->
    case os:type() of
	{unix, _} ->
	    Cmd = io_lib:format("lsof -i4UDP@~p:~p", [Address, Port]),
	    case os:cmd(Cmd) of
		[] ->
		    false;
		_ ->
		    true
	    end;
	_ ->
	    true
    end.
